package org.opens.javaskill.design;

import java.util.LinkedList;

/**
 * 功能:
 * <p>备忘录设计模式</p>
 * 说明:
 * <pre>
 *  1. 该模式的核心是-将每一个状态存储下来, 例如在编辑时分为undo, redo两个操作, 则需要设置两个栈结构来存储每一次undo, redo之前的状态, 然后
 *  在undo是将之前的装态存储到redo的栈中, 在执行redo时就可以进行恢复了, redo操作同样也是.
 * </pre>
 */
public class MementoPattern {

    /**
     * 输出:
     * ```
     * s1
     * s2
     * s3
     * s3
     * s2
     * s1
     * s1
     * 执行redo操作
     * s1
     * s2
     * s3
     * s3
     * s3
     * ```
     * 说明:
     *      1. Originator每次作出操作, 然后输出其state状态都会发现确实发生了变化.
     *      2. back方法用来返回上一个状态.
     *      3. 目前这种写法有一个问题就是-由于是自动保存, 所以在用户做出一个修改state操作之后, 就会被记录到undo列表中, 然后第一次恢复
     *      的快照其实是他自己, redo同样.
     */
    public static void main(String[] args) {
        Originator originator = new Originator();

        originator.setState("s1");
        System.out.println(originator.getState());
        originator.setState("s2");
        System.out.println(originator.getState());
        originator.setState("s3");
        System.out.println(originator.getState());

        // 模拟撤销
        originator.undo();
        System.out.println(originator.getState());
        // 模拟撤销
        originator.undo();
        System.out.println(originator.getState());
        // 模拟撤销
        originator.undo();
        System.out.println(originator.getState());
        // 模拟撤销, 撤销达到快照保存最大深度, 已经不再可以恢复.
        originator.undo();
        System.out.println(originator.getState());

        System.out.println("执行redo操作");
        originator.redo();
        System.out.println(originator.getState());
        originator.redo();
        System.out.println(originator.getState());
        originator.redo();
        System.out.println(originator.getState());
        originator.redo();
        System.out.println(originator.getState());
        originator.redo();
        System.out.println(originator.getState());
    }

}

/**
 * 说明:
 *      1. 这个相当于是操作员, 执行什么操作是由这个类的实例决定的.
 */
class Originator {

    private String state;

    private CareTaker careTaker = new CareTaker();
    private CareTaker redoCareTaker = new CareTaker();

    /**
     * 说明:
     *      1. 在设置state字段的时候, 会直接保存快照, 这里是为了效果, 实际中这个操作应该放到一个用户主动触发的保存操作或者系统定时触发
     *      的自动保存操作中.
     */
    public void setState(String state) {
        this.state = state;
        this.careTaker.add(new Memento(state));
    }

    public String getState() {
        return state;
    }

    public void undo() {
        // 从快照管理器中取出快照并恢复, 如果是null就不恢复了.
        Memento first = this.careTaker.getFirst();
        if ( first != null ) {
            this.redoCareTaker.add(first);
            this.state = first.getState();
        }
    }

    public void redo() {
        Memento first = this.redoCareTaker.getFirst();
        if ( first != null ) {
            this.careTaker.add(first);
            this.state = first.getState();
        }
    }

    /**
     * 说明:
     *      1. 这个类专门用来管理Memento, 不可以对mementoList中保存的快照信息做修改, 只负责获取与保存;
     *          - 迪米特法则的特例.
     *      2. 这个快照管理器使用LinkedList模拟的栈结构-先进后出, 后进先出.
     *          - 原理是存储和取出都控制在链表的一端进行(头部或者尾部).
     */
    private static class CareTaker {

        private LinkedList<Memento> mementoList = new LinkedList<>();

        // 向快照管理器中添加快照信息
        public void add(Memento memento) {
            this.mementoList.addFirst(memento);
        }

        // 由于存储是从头部存, 则取时需要从头部取
        public Memento getFirst() {
            return this.mementoList.pollFirst();
        }

    }

    /**
     * 说明:
     *      1. 这个类就是将要保存的快照对象, 也是快照恢复对象.
     *      2. 需要恢复的信息都需要记录在这个类的实例对象中.
     */
    private static class Memento {

        private String state;

        public Memento(String state) {
            this.state = state;
        }

        public String getState() {
            return state;
        }

    }

}



